﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using Microsoft.Xna.Framework;

namespace Tomahawk.Runtime.Logic.UI
{

    /// <summary>
    /// Delegate for focus events
    /// </summary>
    public delegate void FocusDelegate(XUIPlaceableControl control);

    /// <summary>
    /// Delefate for Value related events
    /// </summary>
    public delegate void ValueDelegate(XUIPlaceableControl control, float PreviousValue, float NewValue);

    /// <summary>
    /// Represents a user interface object that can be placed inside an UIScene.
    /// This kind of controls has screen coordinates and other properties to control its positioning.
    /// They can be also focusable.
    /// </summary>
    public class XUIPlaceableControl : XUIControl
    {

        #region "Enumeration"

        /// <summary>
        /// Available docking modes. 
        /// This controls how the control is shaped against his parent.
        /// </summary>
        public enum DockingModes
        {
            Top,
            TopLeft,
            TopRight,
            Left,
            Right,
            Bottom,
            BottomLeft,
            BottomRight,
            Fill
        }

        #endregion

        #region "Serializable Properties"

        /// <summary>
        /// This structs stores the positioning information for the control
        /// </summary>
        public class PositioningInfo
        {
            [XmlAttribute("top")]
            public float Top = 0.0f;

            [XmlAttribute("left")]
            public float Left = 0.0f;

            [XmlAttribute("width")]
            public float Width = 0.0f;

            [XmlAttribute("height")]
            public float Height = 0.0f;

            [XmlAttribute("rotation")]
            public float Rotation = 0.0f;

            [XmlAttribute("scaleX")]
            public float ScaleX = 1.0f;

            [XmlAttribute("scaleY")]
            public float ScaleY = 1.0f;

            [XmlAttribute("originX")]
            public float OriginX = 0.0f;

            [XmlAttribute("originY")]
            public float OriginY = 0.0f;

            [XmlAttribute("docking")]
            public DockingModes Docking = DockingModes.TopLeft;

            [XmlAttribute("depth")]
            public float Depth = 0.0f;
        }

        /// <summary>
        /// Position of the object relative to its parent
        /// </summary>
        [XmlElement("Position")]
        public PositioningInfo Position = new PositioningInfo();

        /// <summary>
        /// Effect channel used by the sprites of this control
        /// </summary>
        [XmlElement("EffectChannel")]
        public string EffectChannel = "Default";

        /// <summary>
        /// Struct to handle focusable controls references
        /// </summary>
        public class FocusReference
        {
            [XmlAttribute("left")]
            public string Left;

            [XmlAttribute("right")]
            public string Right;

            [XmlAttribute("up")]
            public string Up;

            [XmlAttribute("down")]
            public string Down;
        }

        /// <summary>
        /// References for focusable controls
        /// </summary>
        [XmlElement("Focus")]
        public FocusReference FocusReferences = new FocusReference();

        /// <summary>
        /// Is a focusable control?
        /// </summary>
        [XmlElement("CanFocus")]
        public bool CanHaveFocus = true;

        #endregion

        /// <summary>
        /// Make this object pseudo-abstract by protecting the constructor
        /// </summary>
        public XUIPlaceableControl()
        {
        }

        /// <summary>
        /// Initialization
        /// </summary>
        protected override void OnInitialize()
        {
            base.OnInitialize();
            
            //compute positioining
            this.computePosition();

            //default navigation in both vertically and horizontally directions
            this.CanNavigateHorizontally = true;
            this.CanNavigateVertically = true;
            this.CanActivate = true;
        }

        #region "Positioning"

        private bool positionDirty = true;
        private bool spritesDirty = true;

        private Rectangle parentRenderRect;
        private float parentRenderRot;
        private Vector2 parentRenderScale;
        private Vector2 parentRenderOrigin;

        /// <summary>
        /// Computed render position
        /// </summary>
        protected Rectangle renderRect;

        /// <summary>
        /// Computed render rotation
        /// </summary>
        protected float renderRot;

        /// <summary>
        /// Computed render scale
        /// </summary>
        protected Vector2 renderScale;

        /// <summary>
        /// Computed render origin
        /// </summary>
        protected Vector2 renderOrigin;

        /// <summary>
        /// Use this method to update control position
        /// </summary>
        public void SetPosition(float newLeft, float newTop)
        {
            this.Position.Left = newLeft;
            this.Position.Top = newTop;
            this.positionDirty = true;

            //notify derived class
            this.OnPositionChange();
        }

        /// <summary>
        /// Use this method to update control position
        /// </summary>
        public void SetPosition(float newLeft, float newTop, float newWidth, float newHeight)
        {
            this.Position.Left = newLeft;
            this.Position.Top = newTop;
            this.Position.Width = newWidth;
            this.Position.Height = newHeight;
            this.positionDirty = true;

            //notify derived class
            this.OnPositionChange();
        }

        /// <summary>
        /// Use this method to update control position
        /// </summary>
        public void SetPosition(float newLeft, float newTop, float newWidth, float newHeight, float newDepth)
        {
            this.Position.Left = newLeft;
            this.Position.Top = newTop;
            this.Position.Width = newWidth;
            this.Position.Height = newHeight;
            this.Position.Depth = newDepth;
            this.positionDirty = true;

            //notify derived class
            this.OnPositionChange();
        }

        /// <summary>
        /// Use this method to update control rotation
        /// </summary>
        public void SetRotation(float newRotation, Vector2 newOrigin)
        {
            this.Position.Rotation = newRotation;
            this.Position.OriginX = newOrigin.X;
            this.Position.OriginY = newOrigin.Y;
            this.positionDirty = true;

            //notify derived class
            this.OnPositionChange();
        }

        /// <summary>
        /// Use this method to update control scale
        /// </summary>
        public void SetScale(Vector2 newScale, Vector2 newOrigin)
        {
            this.Position.ScaleX = newScale.X;
            this.Position.ScaleY = newScale.Y;
            this.Position.OriginX = newOrigin.X;
            this.Position.OriginY = newOrigin.Y;
            this.positionDirty = true;

            //notify derived class
            this.OnPositionChange();
        }

        /// <summary>
        /// Override this to provide custom position change logic
        /// </summary>
        protected virtual void OnPositionChange()
        {
        }

        /// <summary>
        /// Compute the new position
        /// </summary>
        protected void computePosition()
        {
            //
            //NOTE: All asignations are inlined for performance reasons
            //

            //use viewport's size as default parent's size/position
            this.parentRenderRect.X = 0;
            this.parentRenderRect.Y = 0;
            this.parentRenderRect.Width = Engine.Instance.EngineConfiguration._Video.virtualCanvasWidth;
            this.parentRenderRect.Height = Engine.Instance.EngineConfiguration._Video.virtualCanvasHeight;

            this.parentRenderRot = 0.0f;
            this.parentRenderScale = Vector2.One;
            this.parentRenderOrigin = Vector2.Zero;

            //get parent's rect (if any)
            XUIPlaceableControl placeableParent = this.parent as XUIPlaceableControl;
            if (placeableParent != null) 
            {
                this.parentRenderRect.X = placeableParent.renderRect.X;
                this.parentRenderRect.Y = placeableParent.renderRect.Y;
                this.parentRenderRect.Width = placeableParent.renderRect.Width;
                this.parentRenderRect.Height = placeableParent.renderRect.Height;
                this.parentRenderRot = placeableParent.renderRot;
                this.parentRenderScale = placeableParent.renderScale;
                this.parentRenderOrigin = placeableParent.parentRenderOrigin;
            }

            Vector2 offset = Vector2.Zero;

            //compute current rect
            switch (this.Position.Docking)
            {
                case DockingModes.Top:
                    offset.X = 0.0f;
                    offset.Y = 0.0f;
                    this.renderRect.Width = parentRenderRect.Width;
                    this.renderRect.Height = (int)this.Position.Height;
                    break;

                case DockingModes.Bottom:
                    offset.X = 0.0f;
                    offset.Y = (parentRenderRect.Height - this.Position.Height);
                    this.renderRect.Width = parentRenderRect.Width;
                    this.renderRect.Height = (int)this.Position.Height;
                    break;

                case DockingModes.Left:
                    offset.X = 0.0f;
                    offset.Y = (parentRenderRect.Height - this.Position.Height);
                    this.renderRect.Width = parentRenderRect.Width;
                    this.renderRect.Height = (int)this.Position.Height;
                    break;

                case DockingModes.Right:
                    offset.X = (parentRenderRect.Width - this.Position.Width);
                    offset.Y = 0.0f;
                    this.renderRect.Width = (int)this.Position.Width;
                    this.renderRect.Height = parentRenderRect.Height;
                    break;

                case DockingModes.Fill:
                    offset.X = 0.0f;
                    offset.Y = 0.0f;
                    this.renderRect.Width = parentRenderRect.Width;
                    this.renderRect.Height = parentRenderRect.Height;
                    break;

                case DockingModes.TopLeft:
                    offset.X = this.Position.Left;
                    offset.Y = this.Position.Top;
                    this.renderRect.Width = (int)(this.Position.Width);
                    this.renderRect.Height = (int)(this.Position.Height);
                    break;

                case DockingModes.TopRight:
                    offset.X = (parentRenderRect.Width - (this.Position.Width + this.Position.Left));
                    offset.Y = this.Position.Top;
                    this.renderRect.Width = (int)(this.Position.Width);
                    this.renderRect.Height = (int)(this.Position.Height);
                    break;

                case DockingModes.BottomLeft:
                    offset.X = this.Position.Left;
                    offset.Y = (parentRenderRect.Height - (this.Position.Height + this.Position.Top));
                    this.renderRect.Width = (int)(this.Position.Width);
                    this.renderRect.Height = (int)(this.Position.Height);
                    break;

                case DockingModes.BottomRight:
                    offset.X = (parentRenderRect.Width - (this.Position.Width + this.Position.Left));
                    offset.Y = (parentRenderRect.Height - (this.Position.Height + this.Position.Top));
                    this.renderRect.Width = (int)(this.Position.Width);
                    this.renderRect.Height = (int)(this.Position.Height);
                    break;
            }

            //compute current scale
            Vector2 scale = Vector2.Zero;
            scale.X = this.Position.ScaleX;
            scale.Y = this.Position.ScaleY;
            this.renderScale = this.parentRenderScale * scale;

            //compute current rotation
            this.renderRot = this.parentRenderRot + this.Position.Rotation;

            //compute current position
            float sin = (float)Math.Sin(this.parentRenderRot);
            float cos = (float)Math.Cos(this.parentRenderRot);

            //update render rect
            offset.X *= this.parentRenderScale.X;
            offset.Y *= this.parentRenderScale.Y;
            
            this.renderRect.X = (int)Math.Round(parentRenderRect.X + offset.X * cos - offset.Y * sin);
            this.renderRect.Y = (int)Math.Round(parentRenderRect.Y + offset.Y * cos + offset.X * sin);

            this.renderRect.Width  = (int)(this.renderScale.X * this.Position.Width);
            this.renderRect.Height = (int)(this.renderScale.Y * this.Position.Height);

            //update render origin
            this.renderOrigin.X = (int)(this.Position.OriginX);
            this.renderOrigin.Y = (int)(this.Position.OriginY);

            //compute childs rects
            foreach (XUIControl child in this.ChildControls)
            {
                XUIPlaceableControl placeableChild = child as XUIPlaceableControl;
                if (placeableChild != null)
                    placeableChild.computePosition();
            }

            //mark the sprites as dirty
            this.spritesDirty = true;
        }

        #endregion

        #region "Focus Management"

        private bool focused = false;

        public bool Focused
        {
            get{ return focused;}
        }

        internal void Unfocus()
        {
            this.focused = false;
            this.OnUnfocus();
        }

        public event FocusDelegate HasGotFocus;
        
        public event FocusDelegate HasLostFocus;

        protected virtual void OnUnfocus() 
        {
            if (HasLostFocus != null) HasLostFocus(this);
        }

        protected virtual void OnFocus()
        {
            if (HasGotFocus != null) HasGotFocus(this);
        }

        /// <summary>
        /// Try focusing the control, if it's not focusable then try the next control
        /// </summary>
        public void Focus()
        {
            this.Focus(false);
        }

        /// <summary>
        /// Try focusing the control, if it's not focusable then try the next control, or the previous one if navigateBackwards is specified
        /// </summary>
        public void Focus(bool navigateBackwards)
        {
            //set this control as the focused one
            if (CanHaveFocus && Visible)
            {
                this.focused = true;
                this.OnFocus();
                this.ParentUIScene.SetFocusedControl(this);
            }
            else
            {   
                //let the parent UIScene determine what's the next control to be focused
                if (navigateBackwards)
                {
                    this.ParentUIScene.FocusNavigatePrev(this);
                }
                else
                {
                    this.ParentUIScene.FocusNavigateNext(this);
                }
            }
        }
        
        [XmlIgnore()]
        public bool CanNavigateHorizontally { get; protected set; }

        [XmlIgnore()]
        public bool CanNavigateVertically { get; protected set; }

        [XmlIgnore()]
        public bool CanActivate { get; protected set; }

        #endregion

        #region "User activation"

        /// <summary>
        /// Determine if this controls can handle user activation 
        /// </summary>
        protected bool enabledUserActivation = true;

        /// <summary>
        /// Call this metod to notify the control with an user activation
        /// </summary>
        public void UserActivate()
        {
            if (this.enabledUserActivation)
                this.OnUserActivate();
        }

        /// <summary>
        /// Override this to provide custom activation logic
        /// </summary>
        protected virtual void OnUserActivate() { }

        #endregion

        #region "Rendering"
        /// <summary>
        /// Copy the control position to the sprites
        /// </summary>
        protected virtual void OnPrepareSpritesForRendering()
        {
        }
        
        /// <summary>
        /// Updates the rendering positioning of this control
        /// </summary>
        protected override void OnPreRender()
        {
            if (this.positionDirty)
            {
                computePosition();
                this.positionDirty = false;
            }

            if (this.spritesDirty)
            {
                OnPrepareSpritesForRendering();
                this.spritesDirty = false;
            }

            base.OnPreRender();
        }
        #endregion
    }

}
